package com.lyf.gateway.filter;


import com.lyf.common.api.CommonResult;
import com.lyf.common.api.ResultCode;
import com.lyf.common.utils.JacksonUtil;
import com.lyf.gateway.config.InnerConfig;
import com.lyf.gateway.constant.Constant;
import com.lyf.gateway.constant.ErrCode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.*;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import static com.lyf.gateway.constant.ErrCode.USER_IS_FORBIDDEN;

@Component
@Slf4j
public class AuthFilter implements GlobalFilter, Ordered {

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private InnerConfig innerConfig;
    @Value("${module-conf.auth-url}")
    private String authUrl;
    @Value("${module-conf.verify-url}")
    private String verifyUrl;
    @Value("${swagger.resource.enable}")
    private boolean enable;
    @Value("${module-conf.black-verify-url}")
    private String blackVerifyUrl;
    private static CommonResult forbiddenCommonResult = CommonResult.forbidden(null);

    @Override
    public int getOrder() {
        /**
         * 优先级高一些。token校验失败的请求直接拦截
         */
        return -100;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        if (path.contains(innerConfig.getPrometheusEndPoint())) {
            return chain.filter(exchange);
        }

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        HttpHeaders headers = new HttpHeaders();
        String swaggerSuffix = innerConfig.getSwaggerSuffix();
        if (!enable && path.contains(swaggerSuffix)) {
            log.warn("网关不允许访问聚合swagger：{} ", path);
            return this.forbidden(response, forbiddenCommonResult);
        }
        // 黑名单
        ArrayList<String> refusePaths = innerConfig.getRefusePaths();
        if (refusePaths.stream().anyMatch(el -> path.startsWith(el)) || path.contains(innerConfig.getKeyWord())) {
            // 黑名单
            log.warn("黑名单地址：{} ", path);
            return this.forbidden(response, forbiddenCommonResult);
        }

        ArrayList<String> baggageItemKey = innerConfig.getBaggageItemKey();
        if (baggageItemKey.stream().anyMatch(el -> path.startsWith(el))) {
            // 白名单放行地址
            log.debug("白名单放行地址：{} ", path);
            headers.add(Constant.DOUBLE_CHECK_KEY, String.valueOf(false));
            return chain.filter(exchange);
        }

        MultiValueMap<String, HttpCookie> cookies = request.getCookies();
        String userid = null;
        if (cookies != null) {
            for (String cookie : cookies.keySet()) {
                List<HttpCookie> httpCookies = cookies.get(cookie);
                for (HttpCookie httpCookie : httpCookies) {
                    if (StringUtils.equalsIgnoreCase(httpCookie.getName(), Constant.COOKIE_USERID)) {
                        headers.add(HttpHeaders.COOKIE, httpCookie.getName() + "=" + httpCookie.getValue());
                        userid=httpCookie.getValue();
                    } else if (StringUtils.equalsIgnoreCase(httpCookie.getName(), Constant.COOKIE_USERSIG)) {
                        headers.add(HttpHeaders.COOKIE, httpCookie.getName() + "=" + httpCookie.getValue());
                    }
                }
            }
        }

        headers.add(Constant.HEADER_APPID, "1");
        headers.add(Constant.DOUBLE_CHECK_KEY, String.valueOf(true));
        headers.add(Constant.SOURCE_KEY, Constant.SOURCE_VALUE);

        /**
         * 验证token有效性
         */
        CommonResult commResponse = this.checkToken(headers);

        if (commResponse != null && commResponse.getCode() != ErrCode.SUCCESS.getCode()) {
            final String errMsg = "login verify fail! " + commResponse.getMsg();
            log.warn("{}，请求拦截的地址是：{} ,返回code码:{}", errMsg, path,commResponse.getCode());
            return this.unAuthoriziedResponse(response, errMsg);
        }
        /**
         * 验证用户是否被封禁
         */
        CommonResult verifyBlack = this.verifyBlack(headers);

        if (verifyBlack != null && verifyBlack.getCode() == ResultCode.SUCCESS.getCode()) {
            boolean isBlack = (boolean)verifyBlack.getData();
            if(isBlack){
                log.warn("用户被禁用,用户id:{} ", userid);
                CommonResult<Object> failed = CommonResult.failed(USER_IS_FORBIDDEN, USER_IS_FORBIDDEN.getMsg());
                return this.ok(response,failed);
            }
        }
        return chain.filter(exchange);
    }
    private CommonResult verifyBlack(HttpHeaders headers) {
        try {
            CompletableFuture<CommonResult> completableFuture = CompletableFuture.supplyAsync(
                    () -> {
                        try {
                            HttpEntity<HttpHeaders> objectHttpEntity = new HttpEntity<>(headers);
                            ResponseEntity<CommonResult> responseResponseEntity = restTemplate.exchange(
                                    blackVerifyUrl, HttpMethod.GET, objectHttpEntity, CommonResult.class
                            );
                            return responseResponseEntity.getBody();
                        } catch (Exception e) {
                            log.error("http get verify verifyBlack response throw ex:{}", e.getMessage());
                            return null;
                        }
                    }
            );
            if (completableFuture.get() != null) {
                return completableFuture.get();
            } else {
                return null;
            }
        } catch (Exception e) {
            log.error(" verifyBlack throw ex:{}", e.getMessage());
        }
        return null;
    }
    private Mono<Void> unAuthoriziedResponse(ServerHttpResponse response, String message) {
        HttpStatus httpStatus = HttpStatus.UNAUTHORIZED;
        CommonResult commResponse = new CommonResult(httpStatus.value(), message);

        byte[] data = JacksonUtil.toJsonStringNoException(commResponse).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(data);
        response.setStatusCode(httpStatus);

        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    private Mono<Void> ok(ServerHttpResponse response, CommonResult commonResult) {
        byte[] data = JacksonUtil.toJsonStringNoException(commonResult).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(data);
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    private Mono<Void> forbidden(ServerHttpResponse response, CommonResult commonResult) {
        byte[] data = JacksonUtil.toJsonStringNoException(commonResult).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(data);
        response.setStatusCode(HttpStatus.FORBIDDEN);
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }

    private CommonResult checkToken(HttpHeaders headers) {
        try {
            CompletableFuture<CommonResult> completableFuture = CompletableFuture.supplyAsync(
                    () -> {
                        try {
                            HttpEntity<HttpHeaders> objectHttpEntity = new HttpEntity<>(headers);
                            ResponseEntity<CommonResult> responseResponseEntity = restTemplate.exchange(
                                    authUrl + verifyUrl, HttpMethod.POST, objectHttpEntity, CommonResult.class
                            );
                            return responseResponseEntity.getBody();
                        } catch (Exception e) {
                            log.error("http get verify token response throw ex:{}", e.getMessage());
                            return null;
                        }
                    }
            );
            if (completableFuture.get() != null) {
                return completableFuture.get();
            } else {
                return null;
            }
        } catch (Exception e) {
            log.error("verify token throw ex:{}", e.getMessage());
        }
        return null;
    }
}
